﻿using System;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SQLite;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Npgsql;
using PropertyExpression.Common;
using PropertyExpression.ControllableQuery;
using PropertyExpression.ControllableQuery.Toolkit;

namespace ControllableQuery.Demo.SQLite
{
    [TestClass]
    public class QueryTest
    {
        [TestMethod]
        public void CheckAllQueries()
        {
            CheckAllQueries(() => new SQLiteConnection(@"Data Source=DB.s3db"));
            CheckAllQueries(() => new SqlConnection("Data Source=(local);Initial Catalog=Serverfault;Integrated Security=True"));
            //CheckAllQueries(() => new NpgsqlConnection("Server=127.0.0.1;Port=5432;Database=Test1;User Id=postgres;Password=qwe123456;"));
        }

        private void CheckAllQueries(Func<DbConnection> connectionString)
        {
            DB.ConnectionFunc = connectionString;
            QueryChecker.CheckAllQueries(
                connectionString,
                QueryResultPropertyTypeChecker,
                ParamCreator,
                QueryTypePredicate,
                info => true,
                ChoiceInvocationGetter,
                GetType().Assembly.GetTypes(),
                new DoNothingQRepository(connectionString() is NpgsqlConnection));
        }

        private static ChoiceInvocationGetter ChoiceInvocationGetter
        {
            get
            {
                return (type, context) => {
                    //if (typeof (IMy).Equals(type))
                    //    return typeof (IMy).Assembly.GetTypes().Where(type.IsAssignableFrom)
                    //        .SelectMany(_ => context.GetConstructorInvocations(_));

                    throw new ApplicationException(
                        string.Format("There are no rules to create instances of '{0}' type.", type));
                };
            }
        }

        private static Func<Type, bool> QueryTypePredicate
        {
            get { return type => true; }
        }

        private static ParamCreator ParamCreator
        {
            get
            {
                return type => {
                    var paramType = type.GetGenericArguments().Single();
                    if (typeof (string).Equals(paramType))
                    {
                        return Param.NewString();
                    }
                    else if (typeof (Option<string>).Equals(paramType))
                    {
                        return Param.NewOptionalString();
                    }
                    else if (typeof (Guid).Equals(paramType))
                    {
                        return Param.NewGuid();
                    }
                    else if (typeof (Option<Guid>).Equals(paramType))
                    {
                        return Param.NewOptionalGuid();
                    }
                    else if (typeof (int).Equals(paramType))
                    {
                        return Param.NewInt();
                    }
                    else if (typeof (Option<int>).Equals(paramType))
                    {
                        return Param.NewOptionalInt();
                    }
                    else if (typeof (Option<DateTime>).Equals(paramType))
                    {
                        return Param.NewOptionalDateTime();
                    }
                    else if (typeof (DateTime).Equals(paramType))
                    {
                        return Param.NewDateTime();
                    }
                    else if (paramType.IsGenericTypeAndEquals(typeof (ITableValued<>)))
                    {
                        return createTableValuedParamMethodInfo.MakeGenericMethod(
                            paramType.GetGenericArguments())
                            .Invoke(null, new object[] {});
                    }
                    else if (typeof (float).Equals(paramType))
                    {
                        return Param.NewFloat();
                    }
                    else if (typeof (bool).Equals(paramType))
                    {
                        return Param.NewBool();
                    }
                    else
                    {
                        throw new InvalidOperationException();
                    }
                };
            }
        }

        private static readonly MethodInfo createTableValuedParamMethodInfo =
            QBuilder.GetMethodInfo<Func<Param<ITableValued<object>>>>(() => Param.NewTableValued<object>()).GetGenericMethodDefinition();

        private static QueryResultPropertyTypeChecker QueryResultPropertyTypeChecker
        {
            get
            {
                return (propertyType, dataRow, queryNameFunc) => {
                    if (propertyType.IsClass)
                    {
                        if (dataRow.GetDataType() == propertyType)
                            return;
                    }
                    else
                    {
                        if (dataRow.GetAllowDbNull()
                            && propertyType.IsGenericTypeAndEquals(typeof (Nullable<>))
                            && dataRow.GetDataType() == propertyType.GetGenericArguments().Single())
                            return;
                        else
                        {
                            if (dataRow.GetDataType() == propertyType)
                                return;
                        }
                    }
                    throw new QueryCheckException(
                        string.Format(
                            @"The type of query result property was incorrect ColumnName = '{0}'
QueryType = '{1}'",
                            dataRow.GetColumnName(),
                            queryNameFunc()));
                };
            }
        }

        [AssemblyInitialize]
        public static void AssemblyInit(TestContext context)
        {
            DB.ConnectionFunc = QExecuter.ConnectionFunc;
        }
    }
}